package de.bht.fb6.cg1.exercise4;


import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import org.newdawn.slick.opengl.TextureLoader;

/**
 * This class creates a window with an OpenGL Context and draws a triangle in it. The implementation is inspired by a
 * lwgl example.
 * 
 * @author Stephan Rehfeld
 */
public class OpenGLMain {

    /**
     * The framerate.
     * 
     */
    private static final int FRAMERATE = 60;

    /**
     * This flag is true if the application should run. If it's set to false the application will terminate.
     */
    private static boolean finished;

    /**
     * The translation of the polygon on the x-axis.
     */
    private static float x = 0.0f;

    /**
     * The translation of the polygon on the y-axis.
     */
    private static float y = 0.0f;

    /**
     * The translation of the polygon on the z-axis.
     */
    private static float z = 0.0f;

    /**
     * The rotation around the x-axis.
     */
    private static float rx = 0.0f;

    /**
     * The rotation around the y-axis.
     */
    private static float ry = 0.0f;

    /**
     * The rotation around the z-axis.
     */
    private static float rz = 0.0f;

    private static org.newdawn.slick.opengl.Texture texture;
    /**
     * Roatatings of the robo arm.
     * 
     * first index is the robo component: - 0 is bottom component - 1 is middle component - 2 is top component the
     * second index is the rotation on x,y and z axis.
     */
    private static float[][] roboArm = {
            {0, 0, 0}, // bottom component (x, y, z) axis
            {0, 0, 0}, // middle component (x, y, z) axis
            {0, 0, 0}  // top component (x, y, z) axis
    };
    
    /**
     * temporary bytebuffer.
     */
    private static ByteBuffer temp = ByteBuffer.allocateDirect(16);
    
    private static float[] lightAmbient  = { 0.5f, 0.5f, 0.5f, 1.0f };
    private static float[] lightDiffuse  = { 1.0f, 1.0f, 1.0f, 1.0f };
    private static float[] lightSpecular = { 1.0f, 1.0f, 1.0f, 1.0f };
    private static float[] lightPosition = { 0.0f, 0.0f, 0.0f, 1.0f };

    private static float[] lightSpot         = { 1f, 0f, 0f, 0f };
    private static float[] lightSpotPosition = { 0.0f, 1.0f, 0.0f, 1f };
    
    private static boolean light1 = false;
    private static boolean light2 = false;
    
    /**
     * The main class.
     * 
     * @param args To arguments are consumed.
     */
    public static void main(final String[] args) {
    	temp.order(ByteOrder.nativeOrder());
        try {
            init();
            run();
        } catch (final Exception e) {
            e.printStackTrace(System.err);
        } finally {
            cleanup();
        }
        System.exit(0);
    }

    /**
     * The method creates the the window and sets up the projection matrix, the viewport, and the initial model matrix.
     * 
     * @throws java.lang.Exception
     *             Is thrown on any possible error.
     */
    private static void init() throws Exception {

        Display.setTitle("Exercise 4");
        Display.setVSyncEnabled(true);

        final DisplayMode[] modes = Display.getAvailableDisplayModes();
        for (int i = 0; i < modes.length; ++i) {
            if (modes[i].getWidth() == 800) {
                Display.setDisplayMode(modes[i]);
                break;
            }
        }

        Display.create();

        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glLoadIdentity();
        GL11.glViewport(0, 0, Display.getDisplayMode().getWidth(), Display.getDisplayMode().getHeight());
        GL11.glOrtho(-1, 1, -1, 1, -1.0, 1.0);
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glLoadIdentity();
        
        GL11.glShadeModel(GL11.GL_SMOOTH);
        GL11.glClearColor(0, 0, 0, 0f);
        GL11.glClearDepth(1f);
        //GL11.glDisable(GL11.GL_DEPTH_TEST);
        //GL11.glEnable(GL11.GL_BLEND);
        //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
        GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST);
        GL11.glHint(GL11.GL_POINT_SMOOTH_HINT, GL11.GL_NICEST);
        GL11.glEnable(GL11.GL_TEXTURE_2D);
        GL11.glEnable(GL11.GL_NORMALIZE);
        texture = TextureLoader.getTexture("JPG", new FileInputStream("test1.jpg"));
        texture.bind();
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
    }
    

    /**
     * This method runs the application.
     */
    private static void run() throws Exception{

        while (!finished) {
            Display.update();
            if (Display.isCloseRequested()) {
                finished = true;
            } else if (Display.isActive()) {
                logic();
                render();
                Display.sync(FRAMERATE);
            } else {
                try {
                    Thread.sleep(100);
                } catch (final InterruptedException e) {
                }
                logic();
                if (Display.isVisible() || Display.isDirty()) {
                    render();
                }
            }
        }
    }

    /**
     * The clean up is called if the application fails.
     */
    private static void cleanup() {
        Display.destroy();
    }

    /**
     * The logic of the application. Currently it processes the key input to rotate the polygon.
     */
    private static void logic() {
        if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)) {
            x -= 0.01;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) {
            x += 0.01;
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)) {
            y -= 0.01;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_UP)) {
            y += 0.01;
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_Q)) {
            rx += 1;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_W)) {
            rx -= 1;
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_A)) {
            ry -= 1;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_S)) {
            ry += 1;
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_Y)) {
            rz -= 1;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_X)) {
            rz += 1;
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
            finished = true;
        }
        
        if (Keyboard.isKeyDown(Keyboard.KEY_M)) {
        	light1 = !light1;
        }
        
        if (Keyboard.isKeyDown(Keyboard.KEY_N)) {
        	light2 = !light2;
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_O)) {
            // change back to orthogonal view.
            GL11.glMatrixMode(GL11.GL_PROJECTION);
            GL11.glLoadIdentity();
            GL11.glViewport(0, 0, Display.getDisplayMode().getWidth(), Display.getDisplayMode().getHeight());
            GL11.glOrtho(-1, 1, -1, 1, -1.0, 1.0);
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_P)) {
            // change to perspective mode
            GL11.glMatrixMode(GL11.GL_PROJECTION);
            GL11.glLoadIdentity();

            // sets to perspective mode - 120° view
            GLU.gluPerspective(120.0f, Display.getDisplayMode().getWidth() / Display.getDisplayMode().getHeight(), 1.0f, -2.0f);

            // look from (0;1;-2.4) to (0;0;0)
            GLU.gluLookAt(0f, 1f, -2.4f, 0f, 0f, 0f, 0f, 1f, 0f);
        }

        int component = 0;
        if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) {
            component = 1;
        } else if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
            component = 2;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_T)) {
            ++roboArm[component][0];
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_Z)) {
            ++roboArm[component][1];
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_U)) {
            ++roboArm[component][2];
        }

    }

    /**
     * This method renders the image.
     * 
     * Start to make you changes here!
     */
    private static void render() throws Exception {
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
        
        GL11.glLight(GL11.GL_LIGHT1, GL11.GL_AMBIENT, (FloatBuffer)temp.asFloatBuffer().put(lightAmbient).flip());              
        GL11.glLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, (FloatBuffer)temp.asFloatBuffer().put(lightDiffuse).flip());            
        GL11.glLight(GL11.GL_LIGHT1, GL11.GL_POSITION, (FloatBuffer)temp.asFloatBuffer().put(lightPosition).flip());
        GL11.glLight(GL11.GL_LIGHT1, GL11.GL_SPECULAR, (FloatBuffer)temp.asFloatBuffer().put(lightSpecular).flip());
        
        GL11.glLight(GL11.GL_LIGHT2, GL11.GL_AMBIENT, (FloatBuffer)temp.asFloatBuffer().put(lightSpot).flip());              
        GL11.glLight(GL11.GL_LIGHT2, GL11.GL_DIFFUSE, (FloatBuffer)temp.asFloatBuffer().put(lightDiffuse).flip());
        GL11.glLight(GL11.GL_LIGHT2, GL11.GL_SPECULAR, (FloatBuffer)temp.asFloatBuffer().put(lightSpecular).flip());
        GL11.glLight(GL11.GL_LIGHT2, GL11.GL_SPOT_DIRECTION, (FloatBuffer)temp.asFloatBuffer().put(new float[] {0,0,1,0}).flip());
        
        GL11.glEnable(GL11.GL_LIGHTING);
        
        
        if (light1) GL11.glEnable(GL11.GL_LIGHT1);
        else GL11.glDisable(GL11.GL_LIGHT1);
        
        if (light2) GL11.glEnable(GL11.GL_LIGHT2);
        else GL11.glDisable(GL11.GL_LIGHT2);
        
        texture.bind();
        // Draw the bottom white rectangle
        GL11.glPushMatrix();
        GL11.glTranslatef(0, 0, 0);
        drawWhiteRectangleXZ(4, 4);
        GL11.glPopMatrix();

        // draw colored triangle
        GL11.glPushMatrix();
        GL11.glTranslatef(x, y, z);

        GL11.glRotatef(rx, 1.0f, 0.0f, 0.0f);
        GL11.glRotatef(ry, 0.0f, 1.0f, 0.0f);
        GL11.glRotatef(rz, 0.0f, 0.0f, 1.0f);
/*
        GL11.glBegin(GL11.GL_TRIANGLES);
        GL11.glColor3f(1.0f, 0.0f, 0.0f);
        GL11.glVertex2f(0.0f, 0.3f);

        GL11.glColor3f(0.0f, 1.0f, 0.0f);
        GL11.glVertex2f(-0.3f, -0.3f);

        GL11.glColor3f(0.0f, 0.0f, 1.0f);
        GL11.glVertex2f(0.3f, -0.3f);
        GL11.glEnd();
        */
        GL11.glColor3f(1, 0, 0);
        parseObj(new File("teddy.obj"));
        GL11.glPopMatrix();

        // draw the robo arm
        GL11.glPushMatrix();
        {
            GL11.glRotatef(roboArm[0][0], 1.0f, 0.0f, 0.0f);
            GL11.glRotatef(roboArm[0][1], 0.0f, 1.0f, 0.0f);
            GL11.glRotatef(roboArm[0][2], 0.0f, 0.0f, 1.0f);

            // bottom
            GL11.glPushMatrix();
            {
                GL11.glTranslatef(0, 1f, 0);
                GL11.glRotatef(roboArm[1][0], 1.0f, 0.0f, 0.0f);
                GL11.glRotatef(roboArm[1][1], 0.0f, 1.0f, 0.0f);
                GL11.glRotatef(roboArm[1][2], 0.0f, 0.0f, 1.0f);

                GL11.glPushMatrix();
                {
                    GL11.glTranslatef(0, 1f, 0);
                    GL11.glRotatef(roboArm[2][0], 1.0f, 0.0f, 0.0f);
                    GL11.glRotatef(roboArm[2][1], 0.0f, 1.0f, 0.0f);
                    GL11.glRotatef(roboArm[2][2], 0.0f, 0.0f, 1.0f);
                    
                    drawCube(0.5f, 1, 0.5f);
                    GL11.glLight(GL11.GL_LIGHT2, GL11.GL_POSITION, (FloatBuffer)temp.asFloatBuffer().put(lightSpotPosition).flip());
                    GL11.glPopMatrix();
                }

                drawCube(0.5f, 1, 0.5f);

                GL11.glPopMatrix();
            }

            drawCube(0.5f, 1, 0.5f);
            GL11.glPopMatrix();
        }
        GL11.glPopMatrix();
    }

    /**
     * draws a cube
     * 
     * @param x size on x axis
     * @param y size on y axis
     * @param z size on z axis
     */
    public static void drawCube(float x, float y, float z) {
		//GL11.glColor3f(1f, 0, 0);
        float x0 = -1 * x / 2;
        float y0 = 0;
        float z0 = -1 * x / 2;
        float x1 = x / 2;
        float y1 = y;
        float z1 = z / 2;

        GL11.glBegin(GL11.GL_TRIANGLE_STRIP); // TOP
        GL11.glNormal3f(0, 0, 1);
        GL11.glTexCoord2d(0,0); GL11.glVertex3f(x1, y0, z1);
        GL11.glTexCoord2d(0,1); GL11.glVertex3f(x0, y0, z1);
        GL11.glTexCoord2d(1,0); GL11.glVertex3f(x1, y0, z0);
        GL11.glTexCoord2d(1,1); GL11.glVertex3f(x0, y0, z0);
        GL11.glEnd();
        
        // BOTTOM
        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
        GL11.glTexCoord2d(0,0); GL11.glVertex3f(x1, y1, z1);
        GL11.glTexCoord2d(0,1); GL11.glVertex3f(x0, y1, z1);
        GL11.glTexCoord2d(1,0); GL11.glVertex3f(x1, y1, z0);
        GL11.glTexCoord2d(1,1); GL11.glVertex3f(x0, y1, z0);
        GL11.glEnd();
		
        // FRONT
        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
        GL11.glTexCoord2d(0,0); GL11.glVertex3f(x0, y0, z0);
        GL11.glTexCoord2d(0,1); GL11.glVertex3f(x1, y0, z0);
        GL11.glTexCoord2d(1,0); GL11.glVertex3f(x0, y1, z0);
        GL11.glTexCoord2d(1,1); GL11.glVertex3f(x1, y1, z0);
        GL11.glEnd();
		
        // BACK
        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
        GL11.glTexCoord2d(0,0); GL11.glVertex3f(x0, y0, z1);
        GL11.glTexCoord2d(0,1); GL11.glVertex3f(x1, y0, z1);
        GL11.glTexCoord2d(1,0); GL11.glVertex3f(x0, y1, z1);
        GL11.glTexCoord2d(1,1); GL11.glVertex3f(x1, y1, z1);
        GL11.glEnd();
		
        //LEFT
        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
        GL11.glTexCoord2d(0,0); GL11.glVertex3f(x0, y0, z0);
        GL11.glTexCoord2d(0,1); GL11.glVertex3f(x0, y0, z1);
        GL11.glTexCoord2d(1,0); GL11.glVertex3f(x0, y1, z0);
        GL11.glTexCoord2d(1,1); GL11.glVertex3f(x0, y1, z1);
        GL11.glEnd();
		
        // RIGHT
        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
        GL11.glTexCoord2d(0,0); GL11.glVertex3f(x1, y0, z0);
        GL11.glTexCoord2d(0,1); GL11.glVertex3f(x1, y0, z1);
        GL11.glTexCoord2d(1,0); GL11.glVertex3f(x1, y1, z0);
        GL11.glTexCoord2d(1,1); GL11.glVertex3f(x1, y1, z1);
        GL11.glEnd();
    }

    /**
     * draw a rectangle on XY axis.
     * 
     * start from 0;0;0
     * 
     * @param x size on x axis
     * @param y size on y axis
     */
    public static void drawRectangleXY(float x, float y) {
        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
        float x1 = x / 2;
        float y1 = y / 2;
        float x0 = x1 * -1;
        float y0 = y1 * -1;

        GL11.glColor3f(1f, 0, 0);
        GL11.glVertex3f(x0, y0, 0);
        GL11.glVertex3f(x1, y0, 0);
        GL11.glVertex3f(x0, y1, 0);
        GL11.glVertex3f(x1, y1, 0);
        GL11.glEnd();
    }

    /**
     * draw a rectangle on XZ axis.
     * 
     * start from 0;0;0
     * 
     * @param x size on x axis
     * @param z size on z axis
     */
    public static void drawRectangleXZ(float x, float z) {
        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);

        float x1 = x / 2;
        float z1 = z / 2;
        float x0 = x1 * -1;
        float z0 = z1 * -1;

        GL11.glColor3f(1f, 0, 0);
        GL11.glVertex3f(x0, 0, z0);
        GL11.glVertex3f(x1, 0, z0);
        GL11.glVertex3f(x0, 0, z1);
        GL11.glVertex3f(x1, 0, z1);
        GL11.glEnd();
    }

    /**
     * draw a white rectangle on XZ axis.
     * 
     * start from 0;0;0
     * 
     * @param x size on x axis
     * @param z size on z axis
     */
    public static void drawWhiteRectangleXZ(float x, float z) {
        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);

        float x1 = x / 2;
        float z1 = z / 2;
        float x0 = x1 * -1;
        float z0 = z1 * -1;

        GL11.glColor3f(1f, 1f, 1f);
        GL11.glVertex3f(x0, 0, z0);
        GL11.glVertex3f(x1, 0, z0);
        GL11.glVertex3f(x0, 0, z1);
        GL11.glVertex3f(x1, 0, z1);
        GL11.glEnd();
    }

    /**
     * draw a rectangle on YZ axis.
     * 
     * start from 0;0;0
     * 
     * @param y size on y axis
     * @param z size on z axis
     */
    public static void drawRectangleYZ(float y, float z) {
        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);

        float y1 = y / 2;
        float z1 = z / 2;
        float y0 = y1 * -1;
        float z0 = z1 * -1;

        GL11.glColor3f(1f, 0, 0);
        GL11.glVertex3f(0, y0, z0);
        GL11.glVertex3f(0, y1, z0);
        GL11.glVertex3f(0, y0, z1);
        GL11.glVertex3f(0, y1, z1);
        GL11.glEnd();
    }
    
    public static Map<Integer, Koord> getKoords(File f) throws Exception {
    	FileInputStream in = new FileInputStream(f);
    	StringBuffer text = new StringBuffer();
    	int get;
    	while ( (get = in.read()) != -1 ) {
    		text.append((char) get);
    	}
    	return getKoords(text.toString());
    }
    
    public static Map<Integer, Koord> getKoords(String content) throws Exception{
    	String[] lines = content.split("\n");
    	Map<Integer, Koord> koords = new HashMap<Integer, Koord>();
    	int koord = 0;
    	for (String line : lines) {
			line = line.replaceAll("#.*", "").trim();
			if (line.isEmpty()) continue;
			
			String[] components = line.split("\\s");
			
			double[] edge = new double[components.length-1];
			
			for (int i=1; i < components.length; i++) {
				edge[i-1] = new Double(components[i]);
			}
			
			Koord current = Koord.create(components[0], edge);
			if (current != null) {
				koords.put(koord, current);
				koord++;
				continue;
			}
		}
    	return koords;
    }
    
    public static List<List<Integer>> getInstruct(File f) throws Exception {
    	FileInputStream in = new FileInputStream(f);
    	StringBuffer text = new StringBuffer();
    	int get;
    	while ( (get = in.read()) != -1 ) {
    		text.append((char) get);
    	}
    	return getInstruct(text.toString());
    }
    
    public static List<List<Integer>> getInstruct(String content) {
    	
    	String[] lines = content.split("\n");
    	List<List<Integer>> result = new ArrayList<List<Integer>>();
    	
    	for (String line : lines) {
			line = line.replaceAll("#.*", "").trim();
			if (line.length() == 0) continue;
			
			String[] components = line.split("\\s");
			if (!components[0].equals("f")) continue;
			
			List<Integer> cur = new ArrayList<Integer>();
			for (int i=1; i<components.length; i++) {
				 cur.add(new Integer(components[i]));
			}
			result.add(cur);
		}
    	return result;
    	
    }
    
    private static List<List<Integer>> instruct = null;
    private static Map<Integer, Koord> koords = null;
    
    
    public static void parseObj(File f) throws Exception {
    	if (instruct == null) instruct = getInstruct(f);
    	if (koords == null) koords = getKoords(f);
    	
    	for (List<Integer> inst : instruct) {
    		GL11.glBegin(GL11.GL_TRIANGLES);
			for (Integer point : inst) {
				GL11.glColor3f(1, 0, 0);
				if (koords.get(point) == null) continue;
				koords.get(point).draw();
			}
			GL11.glEnd();
		}
    }
}
